package cn.learn.office.excel_v3.write;

import cn.learn.office.excel_v3.write.sort.HandlerExcelColumnSort;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * excel导出
 *
 * @author huangyezhan
 * @version 3.0
 * @date 2021年05月21日16:45
 */
@Slf4j
@UtilityClass
public class WriteExcelUtil {

    public void exportExcel(ExcelExportConfig config, HttpServletResponse response) {
        String excelName = getExcelName(config);
        String filename;
        try (
                OutputStream out = response.getOutputStream();
                Workbook wb = creatExcel(config)
        ) {
            filename = new String(excelName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            wb.write(out);
        } catch (Exception e) {
            log.error("导出单sheet异常：{}", e.getMessage());
        }
    }

    public void exportExcel(List<ExcelExportConfig> list, HttpServletResponse response) {
        String excelName = getExcelName(list.get(0));
        String filename;
        try (
                OutputStream out = response.getOutputStream();
                Workbook wb = creatExcel(list)
        ) {
            filename = new String(excelName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            // 设置强制下载不打开
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            wb.write(out);
        } catch (Exception e) {
            log.error("导出多sheet异常：{}", e.getMessage());
        }
    }

    /**
     * 获取Excel名
     */
    private String getExcelName(ExcelExportConfig config) {
        String excelName = config.getExcelName();
        String excelType = config.getExcelType().getType();
        if (!excelName.endsWith(excelType)) {
            return excelName + excelType;
        }
        return excelName;
    }

    /**
     * 创建Excel
     *
     * @param list excel的配置信息，多sheet页
     */
    public Workbook creatExcel(List<ExcelExportConfig> list) {
        Workbook wb;
        if (ExcelExportConfig.ExcelType.XLS == list.get(0).getExcelType()) {
            wb = new HSSFWorkbook();
        } else {
            wb = new XSSFWorkbook();
        }
        for (ExcelExportConfig config : list) {
            String sheetName = config.getSheetName();
            Sheet  sheet     = wb.createSheet(sheetName);
            creatSheet(sheet, config);
        }
        return wb;
    }

    /**
     * 创建Excel
     *
     * @param config excel的配置信息，单sheet页面
     */
    public Workbook creatExcel(ExcelExportConfig config) {
        Workbook wb;
        if (ExcelExportConfig.ExcelType.XLS == config.getExcelType()) {
            wb = new HSSFWorkbook();
        } else {
            wb = new XSSFWorkbook();
        }
        String sheetName = config.getSheetName();
        Sheet  sheet     = wb.createSheet(sheetName);
        creatSheet(sheet, config);
        return wb;
    }

    /**
     * 可能有多个sheet页
     */
    public static void creatSheet(Sheet sheet, ExcelExportConfig config) {
        // 获取不同排序策略的对象，调用对应的列名去排序
        ExcelSortFactory       instance = ExcelSortFactory.getInstance();
        HandlerExcelColumnSort install  = instance.createExcelSort(config.getHandlerSort());
        install.columnSort(config, sheet);
        // 设置表头数据
        writeHeader(sheet, config);
    }

    /**
     * 写入数据
     *
     * @param o    数据
     * @param cell 单元格对象
     */
    public void writeContent(Object o, Cell cell) {
        String type;
        if (o != null) {
            type = o.getClass().getSimpleName();
        } else {
            type = "LEFT";
        }
        switch (type) {
            case "Long":
                cell.setCellValue((Long) o);
                break;
            case "Double":
                cell.setCellValue((Double) o);
                break;
            case "BigDecimal":
                BigDecimal b = (BigDecimal) o;
                cell.setCellValue(b.doubleValue());
                break;
            case "String":
                cell.setCellValue((String) o);
                break;
            case "Timestamp":
                cell.setCellValue(String.valueOf(o));
                break;
            default:
                if (o == null) {
                    cell.setCellValue("");
                } else {
                    cell.setCellValue(String.valueOf(o));
                }
        }
    }

    /**
     * 设置单元格的样式
     */
    public void setCellStyle(Map<ExcelPosition, CellStyle> styleInstance, Cell cell, ExcelPosition excelPosition) {
        CellStyle style = styleInstance.get(excelPosition);
        cell.setCellStyle(style);
    }

    public void writeHeader(Sheet sheet, ExcelExportConfig config) {
        // 表头样式
        Workbook  wb     = sheet.getWorkbook();
        CellStyle create = creatContentStyle(wb, ExcelPosition.CENTER);
        CellStyle style  = createHeaderStyle(wb, create);
        // 填充表头数据
        if (config.getHeaderRowNum() > 0) {
            HeaderRow[]      headerRows = config.getHeaderRow();
            Row              row;
            Cell             cell;
            CellRangeAddress region     = null;
            for (int i = 0; i < config.getHeaderRowNum(); i++) {
                HeaderRow                  headerRow   = headerRows[i];
                List<HeaderRow.HeaderCell> headerCells = headerRow.getHeaderCells();
                // 对应索引行的对象
                row = sheet.createRow(headerCells.get(0).rowIndex());
                for (int j = 0; j < headerCells.size(); j++) {
                    HeaderRow.HeaderCell headerCell = headerCells.get(j);
                    cell = row.createCell(headerCell.colIndex());
                    // 合并行、列
                    int rowIndex = headerCell.rowIndex();
                    int colIndex = headerCell.colIndex();

                    int colspan = headerCell.colspan();
                    int rowspan = headerCell.rowspan();
                    if (colspan > 0 || rowspan > 0) {
                        int lastRowIndex;
                        int lastColIndex;
                        if (rowspan < 1) {
                            lastRowIndex = rowIndex;
                        } else {
                            lastRowIndex = rowIndex + rowspan - 1;
                        }
                        if (colspan < 1) {
                            lastColIndex = colIndex;
                        } else {
                            lastColIndex = colIndex + colspan - 1;
                        }
                        region = new CellRangeAddress(rowIndex, lastRowIndex, colIndex, lastColIndex);
                        sheet.addMergedRegion(region);
                    }
                    cell.setCellValue(headerCell.name());
                    cell.setCellStyle(style);
                    // 列宽
                    if (null == config.getWidth() && i == config.getHeaderRowNum() - 1) {
                        sheet.autoSizeColumn(colIndex, true);
                    } else if (null != config.getWidth() && i == config.getHeaderRowNum() - 1) {
                        sheet.setColumnWidth(colIndex, config.getWidth()[j]);
                    }
                }
            }
        }
    }

    /**
     * 样式实例
     */
    public Map<ExcelPosition, CellStyle> createStyleInstances(Workbook wb) {
        Map<ExcelPosition, CellStyle> map = new HashMap<>(8);
        // 内容样式
        CellStyle styleCenter = creatContentStyle(wb, ExcelPosition.CENTER);
        CellStyle styleLeft   = creatContentStyle(wb, ExcelPosition.LEFT);
        CellStyle styleRight  = creatContentStyle(wb, ExcelPosition.RIGHT);

        // 存储对象
        map.put(ExcelPosition.CENTER, styleCenter);
        map.put(ExcelPosition.LEFT, styleLeft);
        map.put(ExcelPosition.RIGHT, styleRight);
        return map;
    }

    /**
     * Excel表格的统一的常用样式，居中、居左、居右、换行、边框
     *
     * @param wb        wb对象
     * @param alignment 水平对齐方式
     * @return 返回样式对象
     */
    private CellStyle creatContentStyle(Workbook wb, ExcelPosition alignment) {
        CellStyle style = wb.createCellStyle();
        int       value = alignment.getValue();
        switch (value) {
            case 1:
                style.setAlignment(HorizontalAlignment.LEFT);
                break;
            case 2:
                style.setAlignment(HorizontalAlignment.RIGHT);
                break;
            default:
                style.setAlignment(HorizontalAlignment.CENTER);
        }
        // 垂直方向居中
        style.setVerticalAlignment(VerticalAlignment.CENTER);

        // 设置边框,合并单元格的时候有问题(不能用这个实例)
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);

        //设置自动换行
        style.setWrapText(true);
        return style;
    }

    /**
     * 创建表头的统一样式
     */
    private CellStyle createHeaderStyle(Workbook wb, CellStyle style) {
        Font font = wb.createFont();
        font.setFontName("黑体");
        /*
         * 粗体,字体的大小,水平居中,垂直居中
         */
        font.setBold(true);
        font.setFontHeightInPoints((short) 12);
        style.setFont(font);
        // 设置边框,合并单元格的时候有问题(不能用这个实例)
        style.setBorderTop(BorderStyle.MEDIUM);
        style.setBorderLeft(BorderStyle.MEDIUM);
        style.setBorderRight(BorderStyle.MEDIUM);
        style.setBorderBottom(BorderStyle.MEDIUM);
        // 颜色填充
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 前景颜色
        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        // 背景颜色
        style.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        return style;
    }

}
